package pATT.bNEdit.learning;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;

import pATT.bNEdit.base.ExperienceTable;
import pATT.bNEdit.base.Nodo;
import pATT.bNEdit.base.Potencial;
import pATT.bNEdit.base.RedBayes;
import pATT.bNEdit.inference.AlgoritmoInferencia;

/**
* An implementation of the fractional updating method
* presented in Jensen's Bayesian Networks book
 */
@SuppressWarnings("unchecked")
public class FractionalUpdating extends AdaptationAlgorithm {

public void adapt(RedBayes bayesianNetwork,
                    AlgoritmoInferencia inferenceAlgorithm) {
    // for all the nodes
    Enumeration nodesEnum = bayesianNetwork.ListaNodos().elements();
    while (nodesEnum.hasMoreElements()) {
      Nodo node = (Nodo)nodesEnum.nextElement();
      String varName = bayesianNetwork.NombreExt(node);
      // if the node has an experience table
      if (node.hasExperienceTable()) {
        ExperienceTable experienceTable = node.getExperienceTable();
        //get the node's conditional probability table
        Potencial prob = node.getProbabilidadOriginal();
        //get the node's number of states
        int nStates =
            ((Integer)prob.getDominio().getEstados().elements().nextElement()).
            intValue();
        //get the node's number of parent configurations
        int nConfigs = prob.tamanio() / nStates;
        // for each parent configuration
        for (int cfgIndex=0; cfgIndex<nConfigs; cfgIndex++) {
          Map configuration = getConfiguration(node, cfgIndex, nConfigs,
                                               bayesianNetwork);
          double oldExperience = experienceTable.get(cfgIndex);
          double z = inferenceAlgorithm.query(bayesianNetwork, configuration);
          double newExperience = oldExperience +z;
          experienceTable.set(cfgIndex, newExperience);
          if (oldExperience!=0 && newExperience!=0) {
            // for each state of the current variable
            for (int stateIndex = 0; stateIndex < nStates; stateIndex++) {
              int tableIndex = getIndex(nConfigs, stateIndex, cfgIndex);
              double n = oldExperience * prob.get(tableIndex);
              configuration.put(varName, node.getNombreEstado(stateIndex));
              n += z*inferenceAlgorithm.query(bayesianNetwork, new Hashtable());
              prob.set(tableIndex, n / newExperience);
            }
          }
        }
      }
    }
  }

  private int getIndex(int nConfigs, int stateIndex, int cfgIndex) {
    return nConfigs * stateIndex + cfgIndex;
  }

  private Map getConfiguration(Nodo node, int cfgIndex, int nConfigs,
                               RedBayes bayesianNetwork) {
    Map configuration = new Hashtable();
    Enumeration enumStates =
        node.getProbabilidadOriginal().getDominio().getEstados().elements();
    Enumeration enumParents = node.getPadres().elements();
    enumStates.nextElement();
    while (enumStates.hasMoreElements()) {
      int n = ((Integer)enumStates.nextElement()).intValue();
      nConfigs /= n;
      int stateIndex = cfgIndex / nConfigs;
      Nodo parent = (Nodo) enumParents.nextElement();
      String parentName = bayesianNetwork.NombreExt(parent);
      configuration.put(parentName, parent.getNombreEstado(stateIndex));
      cfgIndex %= nConfigs;
    }
    return configuration;
  }
}
